# my_shapes_fixed.py
import math
import matplotlib.pyplot as plt
import matplotlib.patches as patches

EPS = 1e-9

class Shape:
    def area(self): raise NotImplementedError
    def perimeter(self): raise NotImplementedError
    def draw(self, ax): raise NotImplementedError
    def bounds(self):
        """返回 (xmin, xmax, ymin, ymax) 用于自动缩放"""
        raise NotImplementedError

# ---------- Rectangle ----------
class Rectangle(Shape):
    def __init__(self, w, h):
        if w <= 0 or h <= 0:
            raise ValueError("矩形边长必须 > 0")
        self.w = float(w); self.h = float(h)

    def area(self):
        return self.w * self.h

    def perimeter(self):
        return 2 * (self.w + self.h)

    def draw(self, ax):
        rect = patches.Rectangle((-self.w/2, -self.h/2), self.w, self.h, fill=False)
        ax.add_patch(rect)
        self._autoscale(ax)

    def bounds(self):
        return (-self.w/2, self.w/2, -self.h/2, self.h/2)

    def _autoscale(self, ax, pad=0.12):
        xmin, xmax, ymin, ymax = self.bounds()
        dx = max(xmax - xmin, EPS)
        dy = max(ymax - ymin, EPS)
        ax.set_xlim(xmin - dx*pad, xmax + dx*pad)
        ax.set_ylim(ymin - dy*pad, ymax + dy*pad)
        ax.set_aspect('equal')

    def __repr__(self):
        return f"Rectangle(w={self.w}, h={self.h})"

# ---------- Circle ----------
class Circle(Shape):
    def __init__(self, r):
        if r <= 0:
            raise ValueError("半径必须 > 0")
        self.r = float(r)

    def area(self):
        return math.pi * self.r**2

    def perimeter(self):
        return 2 * math.pi * self.r

    def draw(self, ax):
        circ = patches.Circle((0.0, 0.0), radius=self.r, fill=False)
        ax.add_patch(circ)
        self._autoscale(ax)

    def bounds(self):
        return (-self.r, self.r, -self.r, self.r)

    def _autoscale(self, ax, pad=0.12):
        xmin, xmax, ymin, ymax = self.bounds()
        dx = max(xmax - xmin, EPS)
        dy = max(ymax - ymin, EPS)
        ax.set_xlim(xmin - dx*pad, xmax + dx*pad)
        ax.set_ylim(ymin - dy*pad, ymax + dy*pad)
        ax.set_aspect('equal')

    def __repr__(self):
        return f"Circle(r={self.r})"

# ---------- Triangle (sides a=BC, b=CA, c=AB) ----------
class Triangle(Shape):
    def __init__(self, a, b, c):
        # convention: a = length BC, b = CA, c = AB
        self.a = float(a); self.b = float(b); self.c = float(c)
        if not self.is_valid():
            raise ValueError(f"非法三角形边长：a={a}, b={b}, c={c}")

    def is_valid(self):
        a,b,c = self.a, self.b, self.c
        return (a > 0 and b > 0 and c > 0 and
                a + b > c + EPS and a + c > b + EPS and b + c > a + EPS)

    def area(self):
        s = (self.a + self.b + self.c) / 2.0
        area_sq = s * (s - self.a) * (s - self.b) * (s - self.c)
        return math.sqrt(max(0.0, area_sq))

    def perimeter(self):
        return self.a + self.b + self.c

    def coords(self):
        """
        先把 A=(0,0), B=(c,0) （c = AB）
        依据 |AC|=b, |BC|=a 求出 C 的坐标：
        x = (b^2 + c^2 - a^2) / (2*c)
        y = sqrt( b^2 - x^2 )
        最后把三点平移，使质心（centroid）在原点（图形居中显示）
        """
        a,b,c = self.a, self.b, self.c
        A = (0.0, 0.0)
        B = (c, 0.0)
        # guard against degenerate c ~ 0 (shouldn't happen because c>0 validated)
        if abs(c) < EPS:
            raise ValueError("边 c 太小，无法定位点")
        x = (b*b + c*c - a*a) / (2.0 * c)
        y_sq = b*b - x*x
        if y_sq < 0 and y_sq > -1e-9:
            # 允许极小的负数（数值误差），当作 0 处理
            y = 0.0
        elif y_sq < 0:
            raise ValueError("数值上无法计算出三角形高度（可能边长不满足三角形不等式）")
        else:
            y = math.sqrt(y_sq)
        C = (x, y)
        # center (translate by centroid)
        cx = (A[0] + B[0] + C[0]) / 3.0
        cy = (A[1] + B[1] + C[1]) / 3.0
        A = (A[0] - cx, A[1] - cy)
        B = (B[0] - cx, B[1] - cy)
        C = (C[0] - cx, C[1] - cy)
        return [A, B, C]

    def bounds(self):
        pts = self.coords()
        xs = [p[0] for p in pts]; ys = [p[1] for p in pts]
        return (min(xs), max(xs), min(ys), max(ys))

    def draw(self, ax):
        pts = self.coords()
        polygon = patches.Polygon(pts, fill=False)
        ax.add_patch(polygon)
        xmin, xmax, ymin, ymax = self.bounds()
        dx = max(xmax - xmin, EPS)
        dy = max(ymax - ymin, EPS)
        pad = 0.12
        ax.set_xlim(xmin - dx*pad, xmax + dx*pad)
        ax.set_ylim(ymin - dy*pad, ymax + dy*pad)
        ax.set_aspect('equal')

    def __repr__(self):
        return f"Triangle(a={self.a}, b={self.b}, c={self.c})"

# ========== demo / test ==========
if __name__ == "__main__":
    # 示例：你可以替换下面的实例来测试不同输入
    shapes = [
        Rectangle(4, 3),
        Circle(2),
        Triangle(3, 4, 5)
    ]

    # 计算总面积与总周长（严格用 float）
    total_area = sum(s.area() for s in shapes)
    total_perimeter = sum(s.perimeter() for s in shapes)

    print("各图形信息：")
    for s in shapes:
        print(f"  {s!r}  -> 面积={s.area():.6f}, 周长={s.perimeter():.6f}")

    print("\n累计结果：")
    print(f"  总面积 = {total_area:.6f}")
    print(f"  总周长 = {total_perimeter:.6f}")

    # 画图：每个图形一个子图
    n = len(shapes)
    fig, axs = plt.subplots(1, n, figsize=(4*n, 4))
    if n == 1:
        axs = [axs]
    for ax, s in zip(axs, shapes):
        s.draw(ax)
        ax.set_title(f"A={s.area():.3f}, P={s.perimeter():.3f}")
        # 去掉刻度以便观察
        ax.set_xticks([]); ax.set_yticks([])

    plt.tight_layout()
    plt.show()